package com.zdzy.esign;

import java.io.*;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import cn.hutool.core.util.StrUtil;
import net.sf.json.JSONObject;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Component;

@Component
public class ESign {

    /**
     * 应用程序id
     */
    private static final String APP_ID = "7438940320";
    private static final String APP_KEY = "ac22839b24a5dd60d7f289dc01650211";
    private static final String HOST = "https://smlopenapi.esign.cn";

    /**
     * 主要
     *
     * @param args arg游戏
     * @author wu_hua
     * @since 2022/11/10
     */
    public static void main(String[] args) throws Exception {
        // 应用ID
        String appId = APP_ID;
        // 应用密钥
        String appKey = APP_KEY;
        // 接口调用域名
        String host = "https://smlopenapi.esign.cn";

        // 请求签名鉴权-POST请求
//        testPost(appId, appKey, host);

        // 请求签名鉴权-GET请求
//        testGet(appId, appKey, host, accountId);

        String psnAccount = "18687054497";
        String psnId = "f06dd0349a4b483d85f18789e68a0430";

        // 获取个人认证&授权页面链接
//        getPersonIdentityAndAuth(psnAccount, psnId, host);

        // 查询个人认证信息
//        getPersonIdentity(psnId, psnAccount, "", "");

        // 查询个人授权信息
//        getPersonAuth(psnId);
    }

    /**
     * 签署流程-发起类
     * 基于文件发起签署
     *
     * @param requestBody 请求体
     *                    具体配置，请根据官方文档（https://open.esign.cn/doc/opendoc/pdf-sign3/su5g42）进行配置
     * @return {@link JSONObject }
     * @author wu_hua
     * @since 2022/11/11
     */
    public JSONObject contractSignByFile(Map requestBody) {
        String path = "/v3/sign-flow/create-by-file";
        String url = HOST + path;

        try {
            // 发送请求
            return sendPostRequest(requestBody, path, url);
        } catch (Exception e) {
            e.printStackTrace();
            String msg = MessageFormat.format("基于文件发起签署调用接口出现异常: {0}", e.getMessage());
            System.out.println(msg);
        }
        return null;
    }

    /**
     * 上传本地文件
     * 调用此方法，请先调用getFileContentMd5，计算文件的MD5值
     *
     * @param requestBody 请求体
     *                    具体请求参数，请根据官方文档（https://open.esign.cn/doc/opendoc/file-and-template3/rlh256）进行配置
     * @return {@link JSONObject }
     * @author wu_hua
     * @since 2022/11/12
     */
    public JSONObject uploadLocalFile(Map requestBody) {
        String path = "/v3/files/file-upload-url";
        String url = HOST + path;

        try {
            // 步骤一
            // 文件的MD5值
            String fileContentMD5 = (String) requestBody.get("contentMd5");

            String contentMD5 = getBodyContentMD5(requestBody.toString());

            String method = "POST";
            String signatureStr = buildSignatureStr(method, contentMD5, path);
            String resSignature = doSignatureBase64(signatureStr, APP_KEY);
            Map<String, String> headers = commonHeader(APP_ID, contentMD5, resSignature);

            JSONObject stepOne = sendPostRequest(requestBody, path, url);
            if ( stepOne.getInt("code") == 0) {

                //步骤二
                Map data = (Map) stepOne.get("data");
                String fileUploadUrl = (String) data.get("fileUploadUrl");

                String contentType = (String) requestBody.get("contentType");
                JSONObject requestData = new JSONObject();
                requestData.put("Content-MD5", fileContentMD5);
                requestData.put("Content-Type", contentType);
                String requestBodyData = requestData.toString();

                String result = HTTPHelper.sendPOST(fileUploadUrl, requestBodyData, headers, "UTF-8");
                JSONObject resultObj = JSONObject.fromObject(result);
                System.out.println("请求返回信息： " + resultObj.toString());
                return resultObj;
            }
            return stepOne;
        } catch (Exception e) {
            e.printStackTrace();
            String msg = MessageFormat.format("上传本地文件调用接口出现异常: {0}", e.getMessage());
            System.out.println(msg);
        }
        return null;
    }

    /**
     * 获取个人认证 & 授权页面链接
     * （psnAccount, psnId二者选一项传入即可）
     *
     * @param requestBody 请求体数据
     *                    具体请求参数，请根据官方文档（https://open.esign.cn/doc/opendoc/auth3/rx8igf）进行配置
     * @return {@link JSONObject }
     * @author wu_hua
     * @since 2022/11/10
     */
    public JSONObject getPersonIdentityAndAuth(Map requestBody) {
        String path = "/v3/psn-auth-url";
        String url = HOST + path;

        try {
            // 请求Body体数据
            return sendPostRequest(requestBody, path, url);
        } catch (Exception e) {
            e.printStackTrace();
            String msg = MessageFormat.format("获取个人认证&授权页面链接调用接口出现异常: {0}", e.getMessage());
            System.out.println(msg);
        }
        return null;
    }

    /**
     * 查询个人认证信息
     * <p>
     * 入参中psnId、psnAccount和psnIdCardNum三个参数只选择一个传入即可查询个人的认证信息。
     * 查询优先级为 psnId > psnAccount > psnIdCardNum
     * 具体请求参数， 请根据官方文档（https://open.esign.cn/doc/opendoc/auth3/vssvtu）进行配置
     *
     * @return {@link JSONObject }
     * @author wu_hua
     * @since 2022/11/10
     */
    public JSONObject getPersonIdentity(Map requestBody) {

        String path = "/v3/persons/identity-info?";

        // 从请求体数据中，解析参数
        try {
            String psnId = (String) requestBody.get("psnId");
            String psnAccount = (String) requestBody.get("psnAccount");
            String psnIDCardNum = (String) requestBody.get("psnIDCardNum");
            String psnIDCardType = (String) requestBody.get("psnIDCardType");

            if (!psnId.equals("")) {
                path = path + "psnId=" + psnId;
            } else if (!psnAccount.equals("")) {
                path = path + "psnAccount=" + psnAccount;
            } else if (!psnIDCardNum.equals("")) {
                if (psnIDCardType.equals("")) {
                    throw new RuntimeException("请传入psnIDCardType");
                }
                path = path + "psnIDCardNum" + psnIDCardNum + "&psnIDCardType=" + psnIDCardType;
            } else {
                throw new RuntimeException("请求参数为空！");
            }

            // 拼接请求url
            String url = HOST + path;

            // GET请求时ContentMD5为""
            return sendGetRequest(path, url);
        } catch (Exception e) {
            e.printStackTrace();
            String msg = MessageFormat.format("获取个人认证调用接口出现异常: {0}", e.getMessage());
            System.out.println(msg);
        }

        return null;
    }

    /**
     * 查询个人授权信息
     *
     * @param psnId 个人账号id
     * @return {@link JSONObject }
     * @author wu_hua
     * @since 2022/11/10
     */
    public JSONObject getPersonAuth(String psnId) {
        String path = StrUtil.format("/v3/persons/{}/authorized-info", psnId);
        String url = HOST + path;

        try {
            return sendGetRequest(path, url);
        } catch (Exception e) {
            e.printStackTrace();
            String msg = MessageFormat.format("获取个人授权调用接口出现异常: {0}", e.getMessage());
            System.out.println(msg);
        }

        return null;

    }

    /**
     * 获取机构认证和授权页面链接
     * 组织机构名称orgName与机构账号orgId二者选一项传入即可；
     *
     * @param requestBody 请求体
     *                    具体请求参数， 请根据官方文档（https://open.esign.cn/doc/opendoc/auth3/kcbdu7）进行配置
     * @return {@link JSONObject }
     * @author wu_hua
     * @since 2022/11/10
     */
    public JSONObject getOrgIdentityAndAuth(Map requestBody) {
        String path = "/v3/org-auth-url";
        String url = HOST + path;

        try {
            // 下列两个参数，二选一传入
            // 组织机构名称
            String orgName = (String) requestBody.get("orgName");
            // 机构账号ID
            String orgId = (String) requestBody.get("orgId");
            // 经办人信息，必填（psnAccount与psnId二选一传入，不可同时传入，也不可同时不传。）
            Map transactorInfo = (Map) requestBody.get("transactorInfo");
            String psnId = (String) transactorInfo.get("psnId");
            String psnAccount = (String) transactorInfo.get("psnAccount");

            if (psnId.equals("") && psnAccount.equals("")) {
                throw new RuntimeException("经办人psnId与psnAccount必须传入一个");
            } else if (!psnId.equals("") && !psnAccount.equals("")) {
                throw new RuntimeException("经办人psnId与psnAccount只能传入一个");
            }
            return sendPostRequest(requestBody, path, url);

        } catch (Exception e) {
            e.printStackTrace();
            String msg = MessageFormat.format("获取机构认证&授权页面链接调用接口出现异常: {0}", e.getMessage());
            System.out.println(msg);
        }
        return null;
    }

    /**
     * 查询组织机构认证信息
     *
     * @param requestBody 请求体
     *                    具体请求参数，请根据官方文档（https://open.esign.cn/doc/opendoc/auth3/xxz4tc）进行配置
     * @return {@link JSONObject }
     * @author wu_hua
     * @since 2022/11/10
     */
    public JSONObject getOrgIdentity(Map requestBody) {
        String path = "/v3/organizations/identity-info?";

        // 从请求体数据中，解析参数
        try {
            String orgId = (String) requestBody.get("orgId");
            String orgName = (String) requestBody.get("orgName");
            String orgIDCardNum = (String) requestBody.get("orgIDCardNum");
            String orgIDCardType = (String) requestBody.get("orgIDCardType");

            if (!orgId.equals("")) {
                path = path + "orgId=" + orgId;
            } else if (!orgName.equals("")) {
                path = path + "orgName=" + orgName;
            } else if (!orgIDCardNum.equals("")) {
                if (orgIDCardType.equals("")) {
                    throw new RuntimeException("请传入orgIDCardType");
                }
                path = path + "psnIDCardNum" + orgIDCardNum + "&orgIDCardType=" + orgIDCardType;
            } else {
                throw new RuntimeException("请求参数为空！");
            }

            // 拼接请求url
            String url = HOST + path;

            // GET请求时ContentMD5为""
            return sendGetRequest(path, url);
        } catch (Exception e) {
            e.printStackTrace();
            String msg = MessageFormat.format("查询个人认证调用接口出现异常: {0}", e.getMessage());
            System.out.println(msg);
        }

        return null;
    }

    /**
     * 查询机构授权信息
     *
     * @param orgId 机构账号ID
     * @return {@link JSONObject }
     * @author wu_hua
     * @since 2022/11/10
     */
    public JSONObject getOrgAuth(String orgId) {
        String path = StrUtil.format("/v3/organizations/{}/authorized-info", orgId);
        String url = HOST + path;

        try {
            // GET请求时ContentMD5为""
            return sendGetRequest(path, url);
        } catch (Exception e) {
            e.printStackTrace();
            String msg = MessageFormat.format("查询机构授权调用接口出现异常: {0}", e.getMessage());
            System.out.println(msg);
        }

        return null;
    }

    /**
     * 查询认证授权流程详情
     *
     * @param authFlowId 认证授权流程ID
     * @return {@link JSONObject }
     * @author wu_hua
     * @since 2022/11/10
     */
    public JSONObject getAuthFlow(String authFlowId) {
        String path = "/v3/auth-flow/" + authFlowId;
        String url = HOST + path;

        try {
            // GET请求时ContentMD5为""
            return sendGetRequest(path, url);
        } catch (Exception e) {
            e.printStackTrace();
            String msg = MessageFormat.format("查询认证授权流程调用接口出现异常: {0}", e.getMessage());
            System.out.println(msg);
        }

        return null;
    }

    /**
     * 封装发送get请求公共代码
     *
     * @param path 路径
     * @param url  请求完整地址
     * @return {@link JSONObject }
     * @author wu_hua
     * @since 2022/11/10
     */
    private JSONObject sendGetRequest(String path, String url) throws Exception {
        // GET请求时ContentMD5为""
        String contentMD5 = "";

        String method = "GET";
        String signatureStr = buildSignatureStr(method, contentMD5, path);
        String resSignature = doSignatureBase64(signatureStr, APP_KEY);
        Map<String, String> headers = commonHeader(APP_ID, contentMD5, resSignature);

        // 发送POST请求
        String result = HTTPHelper.sendGet(url, headers, "UTF-8");
        JSONObject resultObj = JSONObject.fromObject(result);
        System.out.println("请求返回信息： " + resultObj.toString());
        return resultObj;
    }


    /**
     * 封装发送post请求公共代码
     *
     * @param requestBody 请求体数据
     * @param path        路径
     * @param url         完整请求地址
     * @return {@link JSONObject }
     * @author wu_hua
     * @since 2022/11/10
     */
    private JSONObject sendPostRequest(Map requestBody, String path, String url) throws Exception {
        // 请求Body体数据
        String reqBodyData = JSONObject.fromObject(requestBody).toString();
        // 对请求Body体内的数据计算ContentMD5
        String contentMD5 = getBodyContentMD5(reqBodyData);

        String method = "POST";
        String signatureStr = buildSignatureStr(method, contentMD5, path);
        String resSignature = doSignatureBase64(signatureStr, APP_KEY);
        Map<String, String> headers = commonHeader(APP_ID, contentMD5, resSignature);

        // 发送POST请求
        String result = HTTPHelper.sendPOST(url, reqBodyData, headers, "UTF-8");
        JSONObject resultObj = JSONObject.fromObject(result);
        System.out.println("请求返回信息： " + resultObj.toString());
        return resultObj;
    }

    /**
     * @param appId        应用程序id
     * @param contentMD5   内容md5
     * @param reqSignature 要求签名
     * @return {@link Map }<{@link String }, {@link String }>
     * @describe 构建请求头信息
     * @author wu_hua
     * @since 2022/11/06
     */
    private Map<String, String> commonHeader(String appId, String contentMD5, String reqSignature) {
        // 构建请求头
        Map<String, String> header = new HashMap<>();
        header.put("X-Tsign-Open-App-Id", appId);
        header.put("X-Tsign-Open-Auth-Mode", "Signature");
        header.put("X-Tsign-Open-Ca-Signature", reqSignature);
        header.put("X-Tsign-Open-Ca-Timestamp", String.valueOf(timeStamp()));
        header.put("Accept", "*/*");
        header.put("Content-Type", "application/json; charset=UTF-8");
        header.put("Content-MD5", contentMD5);
        System.out.println("header= " + header);
        return header;
    }

    /**
     * 计算请求体内容md5
     *
     * @param bodyData 待计算的消息
     * @return {@link String }
     * @author wu_hua
     * @date 2022/11/08
     */
    public String getBodyContentMD5(String bodyData) throws Exception {
        byte[] md5Bytes = null;
        MessageDigest md5 = null;
        String bodyContentMD5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
            // 计算md5函数
            md5.update(bodyData.getBytes("UTF-8"));
            // 获取文件MD5的二进制数组（128位）
            md5Bytes = md5.digest();
            // 把MD5摘要后的二进制数组md5Bytes使用Base64进行编码（而不是对32位的16进制字符串进行编码）
            bodyContentMD5 = new String(Base64.encodeBase64(md5Bytes), "UTF-8");
        } catch (NoSuchAlgorithmException e) {
            String msg = MessageFormat.format("不支持此算法: {0}", e.getMessage());
            Exception ex = new Exception(msg);
            ex.initCause(e);
            throw ex;
        } catch (UnsupportedEncodingException e) {
            String msg = MessageFormat.format("不支持的字符编码: {0}", e.getMessage());
            Exception ex = new Exception(msg);
            ex.initCause(e);
            throw ex;
        }
        return bodyContentMD5;
    }

    /**
     * 计算文件内容md5
     *
     * @param filePath 文件路径
     * @return {@link String }
     * @author wu_hua
     * @since 2022/11/12
     */
    public String getFileContentMD5(String filePath) throws Exception {
        FileInputStream fis = null;
        byte[] md5Bytes = null;
        String fileContentMD5 = null;
        try {
            File file = new File(filePath);
            fis = new FileInputStream(file);
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            byte[] buffer = new byte[1024];
            int length = -1;
            while ((length = fis.read(buffer, 0, 1024)) != -1) {
                md5.update(buffer, 0, length);
            }
            md5Bytes = md5.digest();
            fis.close();
            fileContentMD5 = new String(Base64.encodeBase64(md5Bytes), "UTF-8");
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return fileContentMD5;
    }

    /**
     * 构建待签名字符串
     *
     * @param method     方法
     * @param contentMD5 内容md5
     * @param path       请求路径
     * @return {@link String } 待签名字符串
     * @author wu_hua
     * @date 2022/11/08
     */
    public String buildSignatureStr(String method, String contentMD5, String path) {
        String accept = "*/*";
        String contentType = "application/json; charset=UTF-8";
        String headers = "";
        String date = "";

        StringBuffer sb = new StringBuffer();
        sb.append(method).append("\n").append(accept).append("\n").append(contentMD5).append("\n")
                .append(contentType).append("\n").append(date).append("\n");
        if ("".equals(headers)) {
            sb.append(headers).append(path);
        } else {
            sb.append(headers).append("\n").append(path);
        }

        // 构建参与请求签名计算的明文
        String plaintext = sb.toString();
        return plaintext;
    }

    /**
     * 计算请求签名值
     *
     * @param message 待计算的消息
     * @param secret  密钥
     * @return HmacSHA256计算后摘要值的Base64编码
     * @throws Exception 加密过程中的异常信息
     */
    public String doSignatureBase64(String message, String secret) throws Exception {
        String algorithm = "HmacSHA256";
        Mac hmacSha256;
        String digestBase64 = null;
        try {
            hmacSha256 = Mac.getInstance(algorithm);
            byte[] keyBytes = secret.getBytes("UTF-8");
            byte[] messageBytes = message.getBytes("UTF-8");
            hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, algorithm));
            // 使用HmacSHA256对二进制数据消息Bytes计算摘要
            byte[] digestBytes = hmacSha256.doFinal(messageBytes);
            // 把摘要后的结果digestBytes转换成十六进制的字符串
            // String digestBase64 = Hex.encodeHexString(digestBytes);
            // 把摘要后的结果digestBytes使用Base64进行编码
            digestBase64 = new String(Base64.encodeBase64(digestBytes), "UTF-8");
        } catch (NoSuchAlgorithmException e) {
            String msg = MessageFormat.format("不支持此算法: {0}", e.getMessage());
            Exception ex = new Exception(msg);
            ex.initCause(e);
            throw ex;
        } catch (UnsupportedEncodingException e) {
            String msg = MessageFormat.format("不支持的字符编码: {0}", e.getMessage());
            Exception ex = new Exception(msg);
            ex.initCause(e);
            throw ex;
        } catch (InvalidKeyException e) {
            String msg = MessageFormat.format("无效的密钥规范: {0}", e.getMessage());
            Exception ex = new Exception(msg);
            ex.initCause(e);
            throw ex;
        }
        System.out.println("Signature= " + digestBase64);
        return digestBase64;
    }

    /**
     * 获取时间戳(毫秒级)
     *
     * @return 毫秒级时间戳, 如 1578446909000
     */
    public long timeStamp() {
        long timeStamp = System.currentTimeMillis();
        return timeStamp;
    }
}